import Dep from "./observe/dep"
import { observe } from "./observe/index"
import Watcher from "./observe/watcher"

export function initState(vm) {
  debugger
  let ops = vm.$options
  if (ops.data) {
    initData(vm)
  }
  // 初始化计算属性
  if (ops.computed) {
    initComputed(vm)
  }
  if (ops.methods) {
    initMethods(vm)
  }
  if (ops.watch) {
    initWatch(vm)
  }
}

function proxy(vm, target, key) {
  // 这里代理的vue实例上的某些属性, 比如vm._data[key] => vm[key]
  Object.defineProperty(vm, key, {
    get() {
      return vm[target][key]
    },
    set(newValue) {
      vm[target][key] = newValue
    }
  })
}

export function initData(vm) {
  let data = vm.$options.data
  data = typeof data === 'function' ? data.call(vm) : data

  vm._data = data

  // 先代理data里的属性
  observe(data)

  // 再代理vm的_data
  Object.keys(data).forEach(key => { proxy(vm, "_data", key) })
}

export function initComputed(vm) {
  let computeds = vm.$options.computed
  // 存放所有的计算属性
  vm._computedWacthers = {}
  Object.keys(computeds).forEach(key => {
    let getter = typeof computeds[key] === "function" ? computeds[key] : computeds[key].get
    // 创建计算属性watcher
    vm._computedWacthers[key] = new Watcher(vm, getter, { lazy: true })
    defineComputed(vm, computeds, key)
  })
}

export function defineComputed(vm, computeds, key) {
  let setter = computeds[key].set // 可能undefined
  Object.defineProperty(vm, key, {
    get() {
      let watcher = vm._computedWacthers[key]
      // 如果是dirty=>脏数据就进行计算,在计算属性依赖的属性发生变化的时候,通知计算属性watcher将dirty置为true,又变为脏数据
      // 而这里需要提到一个巧妙的地方,就是watcher的栈存放的时候一定是渲染普通watcher在前面,计算属性watcher在后面
      // 此时当第一次渲染触发render是调用计算属性的get方法时，走到这里进行依赖收集的时候，刚好是先收集计算属性的wather,再收集渲染watcher
      // 如此每次进行通知dep的所有watcher遍历更新的时候，一定是先更新计算属性watcher的update，会重新取到最新的值，并计算存入缓存，dirty设置为true；
      // 此后才会进行渲染watcher的update，直接取到缓存内最新的计算属性的值
      if (watcher.dirty) {
        // 这时候调计算属性watcher.get，此时就更新了计算属性的值存入缓存，在更新计算属性的值的过程中，会走到其依赖的数据的get内(data内定义的数据)
        // 此时Dep.target指向计算属性watcher,在其依赖的数据的get内,会进行依赖收集,收集的就是Dep.target,也就是计算属性watcher
        // 如此计算属性的watcher就会被收集，此后走完计算属性watcher.get，再把计算属性watcher弹出watchers的栈，此后走完计算属性的get方法
        // 此后还在_render方法内，也就是渲染watcher的update方法内，此时watchers的栈内还有一个渲染watcher，继续在计算属性get内，收集依赖，就可以收集到渲染watcher
        // 详细过程见watcher.js文件内的wathers栈定义语法后的注释
        watcher.calculate()
      }
      if (Dep.target) {
        // 这里计算机属性的watcher已经被弹出栈了，calculate完之后
        // 这里就会收集到渲染watcher
        watcher.depend()
      }
      return watcher.value
    },
    set(newValue) {
      if (setter) {
        setter.call(vm, newValue)
      } else {
        console.error("计算属性必须有set方法才可以修改")
      }
    }
  })
}


export function initMethods(vm) {
  let methods = vm.$options.methods
  for (let key in methods) {
    vm[key] = methods[key]
  }
}


// 就是把watch内的属性进行遍历创建n个watcher
// watcher的getter函数就是取watcher的监听的值（data或computed），此时这个值已经被代理了，取值就走到了这个值get上
// 而这个watcher没有lazy标识，已进入就会立刻进行getter，直接取值，并且把Dep.target设置为这个watcher，之后就进行依赖收集
// 注意，这是在$mount之前进行的，所以还没有进行渲染，所以这个watcher是第一被dep收集的watcher，此后收集计算属性的，最后才是渲染watcher的
// 这是特定的设置好的逻辑顺序：此后进行遍历的时候，最新进行的就是watch里的回调，然后是计算属性的取值缓存，此后才会重新渲染视图
export function initWatch(vm) {
  let watches = vm.$options.watch
  for (let key in watches) {
    let watch = watches[key]
    // 实际上watch还可以传入options这里没有实现这么多
    // 比如监听某个对象，只有对对象直接赋值会触发， 但是如果加入了deep，只要修改对象内的任意一个属性都会触发
    // 这很简单：只需要递归的监听对象内部的所有属性，每个属性的dep都get一下取到当前的Dep.target也就是这个watcher即可

    // 以下是各个watch的写法解析，见watch.html
    if (typeof watch === 'function') {
      vm.$watch(key, watch)
    } else if (Array.isArray(watch)) {
      handleArrayWatch(vm, key, watch)
    } else if (typeof watch === 'object') {
      if (typeof watch["handler"] === 'function') {
        vm.$watch(key, watch["handler"])
      }
    }
  }
}

function handleArrayWatch(vm, key, watch) {
  let cbs = []
  watch.forEach(fn => {
    if (typeof fn === 'function') {
      cbs.push(fn)
    } else if (typeof fn === 'string') {
      cbs.push(vm[fn])
    }

  })
  vm.$watch(key, function () {
    cbs.forEach(cb => cb())
  })
}